{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "228afd48",
   "metadata": {},
   "source": [
    "# WebDataset integration using External Source\n",
    "In this notebook is an example of how one may combine the [webdataset](https://github.com/webdataset/webdataset) with a DALI pipeline, using an external source operator"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8e37d740",
   "metadata": {},
   "source": [
    "## Introduction\n",
    "### Data Representation\n",
    "Web Dataset is a dataset representation that heavily optimizes networked accessed storage performance. At its simplest, it stores the whole dataset in one tarball file, where each sample is represented by one or more entries with the same name but different extensions. This approach improves drive access caching in RAM, since the data is represented sequentially."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5380a878",
   "metadata": {},
   "source": [
    "### Sharding\n",
    "In order to improve distributed storage access and network data transfer, the webdataset employs a strategy called sharding. In this approach, the tarball holding the data is split into several smaller ones, called shards, which allows for fetching from several storage drives at once, and reduces the packet size that has to be transferred via the network."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6810b08b",
   "metadata": {},
   "source": [
    "## Sample Implementation\n",
    "First, let's import the necessary modules and define the locations of the datasets that will be needed later.\n",
    "\n",
    "`DALI_EXTRA_PATH` environment variable should point to the place where the data from [DALI extra repository](https://github.com/NVIDIA/DALI_extra) is downloaded. Please make sure that the proper release tag is checked out.\n",
    "\n",
    "The `tar_dataset_paths` holds the paths to the shards that will be loaded while showing and testing the webdataset loader.\n",
    "\n",
    "`batch_size` is the common batch size for both loaders"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "c6ca5ce2",
   "metadata": {},
   "outputs": [],
   "source": [
    "import nvidia.dali.fn as fn\n",
    "import nvidia.dali as dali\n",
    "import nvidia.dali.types as types\n",
    "import webdataset as wds\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "import glob\n",
    "import os\n",
    "import random\n",
    "import tempfile\n",
    "import tarfile\n",
    "\n",
    "root_path = os.path.join(os.environ[\"DALI_EXTRA_PATH\"], \"db\", \"webdataset\",\n",
    "                         \"MNIST\")\n",
    "tar_dataset_paths = [os.path.join(root_path, data_file)\n",
    "                        for data_file in [\"devel-0.tar\", \"devel-1.tar\",\n",
    "                                          \"devel-2.tar\"]]\n",
    "batch_size = 16"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c4df68f6",
   "metadata": {},
   "source": [
    "Next, let's extract the files that will later be used for comparing the file reader to our custom one.\n",
    "\n",
    "The `folder_dataset_files` holds the paths to the files "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "c4667b45",
   "metadata": {},
   "outputs": [],
   "source": [
    "folder_dataset_root_dir = tempfile.TemporaryDirectory()\n",
    "folder_dataset_dirs = [tempfile.TemporaryDirectory(dir=folder_dataset_root_dir.name)\n",
    "                     for dataset in tar_dataset_paths]\n",
    "folder_dataset_tars = [tarfile.open(dataset) for dataset in tar_dataset_paths]\n",
    "\n",
    "for folder_dataset_tar, folder_dataset_subdir in zip(folder_dataset_tars,\n",
    "                                                     folder_dataset_dirs):\n",
    "    folder_dataset_tar.extractall(path=folder_dataset_subdir.name)\n",
    "\n",
    "folder_dataset_files = [\n",
    "    filepath\n",
    "    for folder_dataset_subdir in folder_dataset_dirs\n",
    "    for filepath in sorted(\n",
    "        glob.glob(os.path.join(folder_dataset_subdir.name, \"*.jpg\")),\n",
    "        key=lambda s: int(s[s.rfind('/') + 1:s.rfind(\".jpg\")])\n",
    "    )\n",
    "]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8870c432",
   "metadata": {},
   "source": [
    "The function below is used to later randomize the output from the dataset. The samples are first stored in a prefetch buffer, and then they're randomly yielded in a generator and replaced by a new sample."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "42b9852e",
   "metadata": {},
   "outputs": [],
   "source": [
    "def buffered_shuffle(generator_factory, initial_fill, seed):\n",
    "    def buffered_shuffle_generator():\n",
    "        nonlocal generator_factory, initial_fill, seed\n",
    "        generator = generator_factory()\n",
    "        # The buffer size must be positive\n",
    "        assert(initial_fill > 0)\n",
    "\n",
    "        # The buffer that will hold the randomized samples\n",
    "        buffer = []\n",
    "\n",
    "        # The random context for preventing side effects\n",
    "        random_context = random.Random(seed)\n",
    "\n",
    "        try:\n",
    "            while len(buffer) < initial_fill: # Fills in the random buffer\n",
    "                buffer.append(next(generator))\n",
    "\n",
    "            # Selects a random sample from the buffer and then fills it back\n",
    "            # in with a new one\n",
    "            while True:\n",
    "                idx = random_context.randint(0, initial_fill-1)\n",
    "\n",
    "                yield buffer[idx]\n",
    "                buffer[idx] = None\n",
    "                buffer[idx] = next(generator)\n",
    "\n",
    "        # When the generator runs out of the samples flushes our the buffer\n",
    "        except StopIteration:\n",
    "            random_context.shuffle(buffer)\n",
    "\n",
    "            while buffer:\n",
    "                # Prevents the one sample that was not filled from being duplicated\n",
    "                if buffer[-1] != None:\n",
    "                    yield buffer[-1]\n",
    "                buffer.pop()\n",
    "    return buffered_shuffle_generator\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "921072ab",
   "metadata": {},
   "source": [
    "The next function is used for padding the last batch with the last sample, in order to make it the same size as all the other ones."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "62b05312",
   "metadata": {},
   "outputs": [],
   "source": [
    "def last_batch_padding(generator_factory, batch_size):\n",
    "    def last_batch_padding_generator():\n",
    "        nonlocal generator_factory, batch_size\n",
    "        generator = generator_factory()\n",
    "        in_batch_idx = 0\n",
    "        last_item = None\n",
    "        try:\n",
    "            # Keeps track of the last sample and the sample number mod batch_size\n",
    "            while True:\n",
    "                if in_batch_idx >= batch_size:\n",
    "                    in_batch_idx -= batch_size\n",
    "                last_item = next(generator)\n",
    "                in_batch_idx += 1\n",
    "                yield last_item\n",
    "        # Repeats the last sample the necessary number of times\n",
    "        except StopIteration:\n",
    "            while in_batch_idx < batch_size:\n",
    "                yield last_item\n",
    "                in_batch_idx += 1\n",
    "    return last_batch_padding_generator"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8911700d",
   "metadata": {},
   "source": [
    "The final function collects all the data into batches in order to be able to have a variable length batch for the last sample"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "4b53d6cf",
   "metadata": {},
   "outputs": [],
   "source": [
    "def collect_batches(generator_factory, batch_size):\n",
    "    def collect_batches_generator():\n",
    "        nonlocal generator_factory, batch_size\n",
    "        generator = generator_factory()\n",
    "        batch = []\n",
    "        try:\n",
    "            while True:\n",
    "                batch.append(next(generator))\n",
    "                if len(batch) == batch_size:\n",
    "                    # Converts tuples of samples into tuples of batches of samples\n",
    "                    yield tuple(map(list, zip(*batch)))\n",
    "                    batch = []\n",
    "        except StopIteration:\n",
    "            if batch is not []:\n",
    "                # Converts tuples of samples into tuples of batches of samples\n",
    "                yield tuple(map(list, zip(*batch)))\n",
    "    return collect_batches_generator"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4fe0049f",
   "metadata": {},
   "source": [
    "And finally the data loader, that configures and returns an [ExternalSource](https://docs.nvidia.com/deeplearning/dali/user-guide/docs/examples/general/data_loading/external_input.html) node.\n",
    "\n",
    "### Keyword Arguments:\n",
    "\n",
    "`paths`: describes the paths to the file/files containing the webdataset, and can be formatted as any data accepted by the `WebDataset`\n",
    "\n",
    "`extensions`: describes the extensions containing the data to be output through the dataset. By default, all image format extensions supported by `WebDataset` are used\n",
    "\n",
    "`random_shuffle`: describes whether to shuffle the data read by the `WebDataset`\n",
    "\n",
    "`initial_fill`: if `random_shuffle` is True describes the buffer size of the data shuffler. Set to 256 by default.\n",
    "\n",
    "`seed`: describes the seed for shuffling the data. Useful for getting consistent results. Set to 0 by default\n",
    "\n",
    "`pad_last_batch`: describes whether to pad the last batch with the final sample to match the regular batch size\n",
    "\n",
    "`read_ahead`: describes whether to prefetch the data into the memory\n",
    "\n",
    "`cycle`: can be either `\"raise\"`, in which case the data loader will throw StopIteration once it reaches the end of the data, in which case the user has to invoke `pipeline.reset()` before the next epoch, or `\"quiet\"`(Default), in which case it will keep looping over the data over and over"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "2479f399",
   "metadata": {},
   "outputs": [],
   "source": [
    "def read_webdataset(\n",
    "    paths,\n",
    "    extensions=None,\n",
    "    random_shuffle=False,\n",
    "    initial_fill=256,\n",
    "    seed=0,\n",
    "    pad_last_batch=False,\n",
    "    read_ahead=False,\n",
    "    cycle=\"quiet\"\n",
    "):\n",
    "    # Parsing the input data\n",
    "    assert(cycle in {\"quiet\", \"raise\", \"no\"})\n",
    "    if extensions == None:\n",
    "        # All supported image formats\n",
    "        extensions = ';'.join([\"jpg\", \"jpeg\", \"img\", \"image\", \"pbm\", \"pgm\", \"png\"])\n",
    "    if type(extensions) == str:\n",
    "        extensions = (extensions,)\n",
    "\n",
    "    # For later information for batch collection and padding\n",
    "    max_batch_size = dali.pipeline.Pipeline.current().max_batch_size\n",
    "\n",
    "    def webdataset_generator():\n",
    "        bytes_np_mapper = (lambda data: np.frombuffer(data, dtype=np.uint8),\n",
    "                           )*len(extensions)\n",
    "        dataset_instance = (wds.WebDataset(paths)\n",
    "                            .to_tuple(*extensions)\n",
    "                            .map_tuple(*bytes_np_mapper))\n",
    "\n",
    "        for sample in dataset_instance:\n",
    "            yield sample\n",
    "\n",
    "    dataset = webdataset_generator\n",
    "\n",
    "    # Adding the buffered shuffling\n",
    "    if random_shuffle:\n",
    "        dataset = buffered_shuffle(dataset, initial_fill, seed)\n",
    "\n",
    "    # Adding the batch padding\n",
    "    if pad_last_batch:\n",
    "        dataset = last_batch_padding(dataset, max_batch_size)\n",
    "\n",
    "    # Collecting the data into batches (possibly undefull)\n",
    "    # Handled by a custom function only when `silent_cycle` is False\n",
    "    if cycle != \"quiet\":\n",
    "        dataset = collect_batches(dataset, max_batch_size)\n",
    "\n",
    "    # Prefetching the data\n",
    "    if read_ahead:\n",
    "        dataset=list(dataset())\n",
    "\n",
    "    return fn.external_source(\n",
    "        source=dataset,\n",
    "        num_outputs=len(extensions),\n",
    "        # If `cycle` is \"quiet\" then batching is handled by the external source\n",
    "        batch=(cycle != \"quiet\"),\n",
    "        cycle=cycle,\n",
    "        dtype=types.UINT8\n",
    "    )"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6c08a04b",
   "metadata": {},
   "source": [
    "We also define a sample data augmentation function which decodes an image, applies a jitter to it and resizes it to 244x244."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "a018552f",
   "metadata": {},
   "outputs": [],
   "source": [
    "def decode_augment(img, seed=0):\n",
    "    img = fn.decoders.image(img)\n",
    "    img = fn.jitter(img.gpu(), seed=seed)\n",
    "    img = fn.resize(img, size=(224, 224))\n",
    "    return img"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "15b9bd5c",
   "metadata": {},
   "source": [
    "## Usage presentation\n",
    "Below we define the sample webdataset pipeline with our `external_source`-based loader, that just chains the previously defined reader and augmentation function together."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "b97ffab9",
   "metadata": {},
   "outputs": [],
   "source": [
    "@dali.pipeline_def(batch_size=batch_size, num_threads=4, device_id=0)\n",
    "def webdataset_pipeline(\n",
    "    paths,\n",
    "    random_shuffle=False,\n",
    "    initial_fill=256,\n",
    "    seed=0,\n",
    "    pad_last_batch=False,\n",
    "    read_ahead=False,\n",
    "    cycle=\"quiet\"\n",
    "):\n",
    "    img, label = read_webdataset(paths=paths,\n",
    "                                 extensions=(\"jpg\", \"cls\"),\n",
    "                                 random_shuffle=random_shuffle,\n",
    "                                 initial_fill=initial_fill,\n",
    "                                 seed=seed,\n",
    "                                 pad_last_batch=pad_last_batch,\n",
    "                                 read_ahead=read_ahead,\n",
    "                                 cycle=cycle)\n",
    "    return decode_augment(img, seed=seed), label"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "db4c395c",
   "metadata": {},
   "source": [
    "The pipeline can then be build with the desired arguments passed through to the data loader"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "7b128aae",
   "metadata": {},
   "outputs": [],
   "source": [
    "pipeline = webdataset_pipeline(\n",
    "    tar_dataset_paths,   # Paths for the sharded dataset\n",
    "    random_shuffle=True, # Random buffered shuffling on\n",
    "    pad_last_batch=False, # Last batch is filled to the full size\n",
    "    read_ahead=False,\n",
    "    cycle=\"raise\")     # All the data is preloaded into the memory\n",
    "pipeline.build()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1c1fb518",
   "metadata": {},
   "source": [
    "And executed, printing the example image using matplotlib"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "fc3a842d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1\n"
     ]
    },
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# If StopIteration is raised, use pipeline.reset() to start a new epoch\n",
    "img, c = pipeline.run()\n",
    "img = img.as_cpu()\n",
    "# Conversion from an array of bytes back to bytes and then to int\n",
    "print(int(bytes(c.as_array()[0])))\n",
    "plt.imshow(img.as_array()[0])\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ee203442",
   "metadata": {},
   "source": [
    "## Checking consistency\n",
    "Here we will check if the custom pipeline for the webdataset matches an equivalent pipeline reading the files from an untarred directory, with `fn.readers.file` reader.\n",
    "\n",
    "First let's define the pipeline to compare against. This is the same pipeline as the one for the webdataset, but instead uses the `fn.readers.file` reader."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "abd839be",
   "metadata": {},
   "outputs": [],
   "source": [
    "@dali.pipeline_def(batch_size=batch_size, num_threads=4, device_id=0)\n",
    "def file_pipeline(files):\n",
    "    img, _ = fn.readers.file(files=files)\n",
    "    return decode_augment(img)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c8018b06",
   "metadata": {},
   "source": [
    "Then let's instantiate and build both pipelines"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "804bce07",
   "metadata": {},
   "outputs": [],
   "source": [
    "webdataset_pipeline_instance = webdataset_pipeline(tar_dataset_paths)\n",
    "webdataset_pipeline_instance.build()\n",
    "file_pipeline_instance = file_pipeline(folder_dataset_files)\n",
    "file_pipeline_instance.build()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2b370da0",
   "metadata": {},
   "source": [
    "\n",
    "And run the comparison loop."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "57e1a773",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "No difference found!\n"
     ]
    }
   ],
   "source": [
    "# The number of batches to sample between the two pipelines\n",
    "num_batches = 10\n",
    "\n",
    "for _ in range(num_batches):\n",
    "    webdataset_pipeline_threw_exception = False\n",
    "    file_pipeline_threw_exception = False\n",
    "\n",
    "    # Try running the webdataset pipeline and check if it has run out of\n",
    "    # the samples\n",
    "    try:\n",
    "        web_img, _ = webdataset_pipeline_instance.run()\n",
    "    except StopIteration:\n",
    "        webdataset_pipeline_threw_exception = True\n",
    "\n",
    "    # Try running the file pipeline and check if it has run out of the samples\n",
    "    try:\n",
    "        (file_img,) = file_pipeline_instance.run()\n",
    "    except StopIteration:\n",
    "        file_pipeline_threw_exception = True\n",
    "\n",
    "    # In case of different number of batches\n",
    "    assert(webdataset_pipeline_threw_exception==file_pipeline_threw_exception)\n",
    "\n",
    "    web_img = web_img.as_cpu().as_array()\n",
    "    file_img = file_img.as_cpu().as_array()\n",
    "\n",
    "    # In case the pipelines give different outputs\n",
    "    np.testing.assert_equal(web_img, file_img)\n",
    "else:\n",
    "    print(\"No difference found!\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
